/*
 * Copyright (c) 2013   Tobias Welther
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package de.tudarmstadt.kom.androidsensorapi.sensormanagement.sensorlistenerholder;

import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import de.tudarmstadt.kom.androidsensorapi.sensor.data.AbstractSensorData;
import de.tudarmstadt.kom.androidsensorapi.sensormanagement.sensorinterface.SensorDataListener;

/**
 * Created by Tobias Welther
 */
public class SensorListenerHolder{

    /**
     * This is a list of all Methods wanting this SensorData
     */
    private final List<ListenerMethods> methodsListening;

    /**
     * This is the Data that this listener Holder will process.
     */
    private final Class<? extends AbstractSensorData> registeredData;

    /**
     * The Queue of the SensorData coming in.
     */
    private final Queue<AbstractSensorData> sensorDataQueue;

    /**
     * The Thread dequeueing the {@link #sensorDataQueue}
     */
    private final SensorHolderDequeueThread dequeueThread;

    /**
     * Creates a sensorListenerHolder witch can process the passed sort of Data.
     *
     * @param registeredData the registered Data this Listener Holder will process.
     */
    public SensorListenerHolder(Class<? extends  AbstractSensorData> registeredData) {
        this.methodsListening = new LinkedList<ListenerMethods>();
        this.registeredData = registeredData;
        this.sensorDataQueue = new ConcurrentLinkedQueue<AbstractSensorData>();

        dequeueThread = new SensorHolderDequeueThread(this);
        dequeueThread.start();
    }

    /**
     * Fires a SensorInterface Data Event.
     */
    protected synchronized void callSensorEvent(){
        if(sensorDataQueue.isEmpty()) return;

        AbstractSensorData data = sensorDataQueue.poll();
        if(!registeredData.isAssignableFrom(data.getClass())){
            return;
        }

        for(ListenerMethods methods : methodsListening){
            methods.callSensorEvent(data);
        }
    }


    /**
     * Pushes the new Data to be called as soon as the Thread collects the Data.
     *
     * @param data to publish to listeners
     */
    public synchronized void queueSensorEvent(AbstractSensorData data){
        this.sensorDataQueue.add(data);
    }


    /**
     * Registers a SensorListener to receive SensorInterface Updates.
     *
     * Warning:<br>
     * The listener is only registered, when min. 1 Method with Annotation:
     * {@link SensorData} is found and this method needs the class this Holder is registered to.
     *
     * @param listener to register.
     */
    public synchronized void registerListener(SensorDataListener listener){
        ListenerMethods listenerMethods = ListenerMethods.generateListenerMethods(listener, this.registeredData);
        if(!listenerMethods.isEmpty()){
            this.methodsListening.add(listenerMethods);
        }
    }

    /**
     * Removes a Listener from all Data passed here.
     *
     * @param listener to remove
     */
    public synchronized void removeListener(SensorDataListener listener){
        List<ListenerMethods> toRemove = new LinkedList<ListenerMethods>();

        for(ListenerMethods methodHolder : this.methodsListening){
            if(methodHolder.isListener(listener)){
                toRemove.add(methodHolder);
            }
        }

        //We need to remove afterwards to avoid concurrent mod exception
        for(ListenerMethods remove : toRemove){
            methodsListening.remove(remove);
        }
    }


    /**
     * This tells if the Queue is empty at the time of thee call.
     */
    protected boolean queueIsEmpty(){
        return methodsListening.isEmpty();
    }


    /**
     * This starts the internal Thread de-queueing the Queue.
     */
    public void stopDequeueing(){
        this.dequeueThread.pauseWork();
    }


    /**
     * This stops the internal Thread de-queueing the Queue.
     */
    public void startDequeueing(){
        this.dequeueThread.continueWork();
    }


    /**
     * Returns the SensorData class this Holder is associated to.
     *
     * @return the class of the SensorData
     */
    public Class<? extends AbstractSensorData> getRegisteredData() {
        return registeredData;
    }
}
